import { Method, createAlova } from 'alova'
import adapterFetch from 'alova/fetch'
import VueHook from 'alova/vue'
import { isArray, isEmpty } from 'lodash'
import { Model } from './model'
import type { RequestConfig } from './types'
import { Message, Notice } from 'vexip-ui'
import router from '@/router'
import { urls } from '@/router/urls'

const alova = createAlova({
  statesHook: VueHook,
  requestAdapter: adapterFetch(),
  responded: { onSuccess },
  cacheFor: { GET: { expire: 0 } }
})

async function onSuccess(response: Response, method: Method) {
  switch (response.status) {
    case 200: {
      if (method.type !== 'GET') {
        Message.success('操作成功')
      }
      break
    }
    case 400: {
      const json: string[] = await response.json()
      Notice.error({ content: json[0] })
      throw response
    }
    case 401:
      router.push(urls.index)
      break
    case 403:
      Notice.error({ title: '你没有权限进行此操作', content: '请重试' })
      throw new Error('FORBIDDEN')
    case 404:
      Notice.error({ title: '未找到资源', content: '请确认提交数据无误' })
      throw new Error('NOTFOUND')
    case 423:
      Notice.error({ title: '请求资源锁定', content: '请重试' })
      throw new Error('LOCKED')
    case 500:
      Notice.error({ title: '服务器内部出错', content: '请致电开发人员' })
      throw response
  }
  const text = await response.text()
  if (text) return JSON.parse(text)
  return {}
}

export function makeRequest(prefix: string = '') {
  return (url: string = '') =>
    class extends BaseRequest {
      static readonly $api = new this()

      constructor() {
        super(`${prefix}${url}`)
      }
    }
}

export class BaseRequest {
  constructor(public baseUrl: string = '') {}

  makeGetConfig<T = void>(config: RequestConfig<T> = {}): [string, any] {
    return [
      makeUrl(this.baseUrl, config?.sub, config.paths),
      {
        params: makeData(config.params),
        headers: config.headers,
        transform: config.typeof ? (data: any) => config.typeof?.inst(data) : config.transform
      }
    ]
  }

  makeConfig<T = void>(config: RequestConfig<T> = {}): [string, any, any] {
    return [
      makeUrl(this.baseUrl, config?.sub, config.paths),
      makeData(config.data, config.includes, config.excludes),
      {
        params: makeData(config.params),
        headers: config.headers,
        transform: config.typeof ? (data: any) => config.typeof?.inst(data) : config.transform
      }
    ]
  }

  get<T = void>(config: RequestConfig<T> = {}) {
    return alova.Get<T>(...this.makeGetConfig(config))
  }

  post<T = void>(config: RequestConfig<T> = {}) {
    return alova.Post<T>(...this.makeConfig(config))
  }

  put<T = void>(config: RequestConfig<T> = {}) {
    return alova.Put<T>(...this.makeConfig(config))
  }

  patch<T = void>(config: RequestConfig<T> = {}) {
    return alova.Patch<T>(...this.makeConfig(config))
  }

  delete<T = void>(config: RequestConfig<T> = {}) {
    return alova.Delete<T>(...this.makeConfig(config))
  }
}

function makeUrl(url: string = '', subUrl: string = '', pathVars: Array<any> = []) {
  const _pathVars = pathVars?.filter((x) => !!x)
  return `${url}${subUrl}${isEmpty(_pathVars) ? '' : '/' + _pathVars.join('/')}`
}

function makeData(data: any, includes?: string[], excludes?: string[]): object | undefined {
  if (!data) return undefined
  let result: Record<string, any> = {}
  if (isArray(data)) {
    data.forEach((x) => Object.assign(result, x instanceof Model ? x.datafy() : x))
  } else {
    result = data instanceof Model ? data.datafy() : data
  }
  const _result: Record<string, any> = {}
  includes?.forEach((x) => (_result[x] = result[x]))
  if (!isEmpty(_result)) {
    result = _result
  }
  excludes?.forEach((x) => delete result[x])
  return result
}
